﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Threading;
using Netbits.Common.Collections;
using Netbits.Common.Conditions;
using Netbits.Common.Logging;
using Netbits.Common.Threading;

namespace Netbits.Common.Network
{
	/// <summary>
	/// Represents a TCP-Server 
	/// </summary>
	public class TcpServer<TClient> : IDisposable where TClient : TcpServerSession
	{
		#region Private Variables

		Lock _Lock;
		LockedListExtended<TcpServerListener> _Listeners;

		SaveEventHandler<TcpServer<TClient>, TClient> _EventClientConnected;
		SaveEventHandler<TcpServer<TClient>, TClient> _EventClientDisconnected;

		LockedListExtended<TClient> _Clients;

		string _Name;
		BindEndPoint _EndPoints;
		int _AcceptedClients;
		int _Backlog;
		int _WorkerPoolSize;
		bool _Disposed;

		ThreadEntry[] _SendWorkerPool;
		ThreadEntry[] _ReceiveWorkerPool;
		ConditionEvent _SendWait;
		ConditionEvent _ReceiveWait;

		#endregion

		#region Constructors

		/// <summary>
		/// Create a new TCP-Server instance with specified factory instance.
		/// </summary>
		public TcpServer( string Name_ )
		{
			_Name = Name_;
			_Lock = new Lock();
			_Clients = new LockedListExtended<TClient>( _Lock );
			_Listeners = new LockedListExtended<TcpServerListener>();
			_EndPoints = new BindEndPoint( IPAddress.Any, 0 );
			_Backlog = 20;
			_WorkerPoolSize = 4;
			_SendWait = new ConditionEvent( 0, 0 );
			_ReceiveWait = new ConditionEvent( 0, 0 );

			_EventClientConnected = new SaveEventHandler<TcpServer<TClient>, TClient>();
			_EventClientDisconnected = new SaveEventHandler<TcpServer<TClient>, TClient>();
		}

		/// <summary>
		/// Create a new TCP-Server instance with specified factory instance.
		/// </summary>
		/// <param name="Name_">Name of this TCP-server</param>
		/// <param name="EndPoints_">The endpoints.</param>
		public TcpServer( string Name_, BindEndPoint EndPoints_ ) : this( Name_ )
		{
			Condition.Requires( EndPoints_, "EndPoints_" ).IsNotNull();
			_EndPoints = EndPoints_;
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// Notified if a new client is connected.
		/// </summary>
		public SaveEventHandler<TcpServer<TClient>, TClient> ClientConnected
		{
			get
			{
				return _EventClientConnected;
			}
		}

		/// <summary>
		/// Notified if the specified client is disconnected.
		/// </summary>
		public SaveEventHandler<TcpServer<TClient>, TClient> ClientDisconnected
		{
			get
			{
				return _EventClientDisconnected;
			}
		}

		/// <summary>
		/// Get or sets the BindEndPoints for this TCP-server instance. 
		/// You must restart the server to apply the changes.
		/// </summary>
		public BindEndPoint EndPoints
		{
			get
			{
				return _EndPoints;
			}
			set
			{
				System.Threading.Interlocked.Exchange( ref _EndPoints, value );
			}
		}

		/// <summary>
		/// Returns a copy of the client-lists
		/// </summary>
		public List<TClient> Clients
		{
			get
			{
				return _Clients.CopyToNewList();
			}
		}

		/// <summary>
		/// Get or sets the maximum pending clients 
		/// </summary>
		public int Backlog
		{
			get
			{
				return _Backlog;
			}
			set
			{
				System.Threading.Interlocked.Exchange( ref _Backlog, value );
			}
		}

		/// <summary>
		/// Get the count of accepted clients.
		/// </summary>
		public int AcceptedClients
		{
			get
			{
				return _AcceptedClients;
			}
		}

		/// <summary>
		/// Get or sets the client-worker-thread-pool (send and receive are splitted) size
		/// Must set before start listening
		/// </summary>
		public int WorkerPoolSize
		{
			get
			{
				return _WorkerPoolSize;
			}
			set
			{
				System.Threading.Interlocked.Exchange( ref _WorkerPoolSize, value );
			}
		}

		/// <summary>
		/// Returns the tcp-server name.
		/// </summary>
		public string Name
		{
			get
			{
				return _Name;
			}
		}

		#endregion

		#region Public Functions

		/// <summary>
		/// Starting all listeners specified by EndPoints.
		/// </summary>
		public virtual void Start()
		{
			try
			{
				if( _Listeners.Count > 0 )
					return;

				if( _EndPoints.Count == 0 )
					throw new Exception( "can't start TCP-Server. no EndPoints defined." );

				_SendWorkerPool = new ThreadEntry[_WorkerPoolSize];
				for( int i = 0; i < _WorkerPoolSize; i++ )
				{
					ThreadEntry te = ThreadManager.Create( string.Format( "TCP-Server {0} - send-worker {1}", _Name, i ) );
					te.IsBackground = true;
					te.Run = _HandleClientDataSend;
					te.HandleNextStep = _SendWait.Event;
					te.Start();

					_SendWorkerPool[i] = te;
				}

				_ReceiveWorkerPool = new ThreadEntry[_WorkerPoolSize];
				for( int i = 0; i < _WorkerPoolSize; i++ )
				{
					ThreadEntry te = ThreadManager.Create( string.Format( "TCP-Server {0} - receive-worker {1}", _Name, i ) );
					te.IsBackground = true;
					te.Run = _HandleClientDataReceive;
					te.HandleNextStep = _ReceiveWait.Event;
					te.Start();

					_SendWorkerPool[i] = te;
				}

				for( int i = 0; i < _EndPoints.Count; i++ )
				{
					TcpServerListener tsl = new TcpServerListener( _EndPoints.Get( i ), _Backlog );
					tsl.AcceptClients.Join( _HandleAcceptClient );
					tsl.Start();
					_Listeners.Add( tsl );
				}
			}
			catch
			{
				Stop();
			}
		}

		/// <summary>
		/// Stop all listener and remove all clients.
		/// </summary>
		public virtual void Stop()
		{
			if( _Listeners.Count == 0 )
				return;

			for( int i = 0; i < _WorkerPoolSize; i++ )
			{
				ThreadEntry te = _SendWorkerPool[i];
				_SendWorkerPool[i] = null;
				te.Dispose();
			}
			_SendWorkerPool = null;

			for( int i = 0; i < _WorkerPoolSize; i++ )
			{
				ThreadEntry te = _ReceiveWorkerPool[i];
				_ReceiveWorkerPool[i] = null;
				te.Dispose();
			}
			_ReceiveWorkerPool = null;

			List<TcpServerListener> lst = _Listeners.CopyToNewList();
			foreach( TcpServerListener listener in lst )
				listener.Stop();
			_Listeners.Clear();

			DisconnectAllClients();
		}

		/// <summary>
		/// Disconnect all clients.
		/// </summary>
		public virtual void DisconnectAllClients()
		{
			List<TClient> clients = _Clients.CopyToNewList();
			foreach( TClient client in clients )
			{
				client.Disconnected.Leave( _HandleClientDisconnected );
				client.Disconnect();
			}
			_Clients.Clear();
		}

		/// <summary>
		/// Send buffer to all connected clients
		/// </summary>
		/// <param name="Buffer_">Buffer to send</param>
		public virtual void SendToAllClients( byte[] Buffer_ )
		{
			SendToAllClients( Buffer_, 0, Buffer_.Length );
		}

		/// <summary>
		/// Send buffer to all connected client by specified index-of and size
		/// </summary>
		/// <param name="Buffer_">Buffer to send</param>
		/// <param name="IndexOf_">Index of buffer to start sending</param>
		/// <param name="Size_">Amount of bytes to send</param>
		public virtual void SendToAllClients( byte[] Buffer_, int IndexOf_, int Size_ )
		{
			SendToAllClients( Buffer_, IndexOf_, Size_, Timeout.Infinite );
		}

		/// <summary>
		/// Send buffer to all connected clients by specified index-of and size with specified maximum timeout
		/// </summary>
		/// <param name="Buffer_">Buffer to send</param>
		/// <param name="IndexOf_">Index of buffer to start sending</param>
		/// <param name="Size_">Amount of bytes to send</param>
		/// <param name="Timeout_">Timeout with maximum wait for send is finished</param>
		public virtual void SendToAllClients( byte[] Buffer_, int IndexOf_, int Size_, int Timeout_ )
		{
			List<TClient> sessions = _Clients.CopyToNewList();
			foreach( TClient tss in sessions )
				tss.Send( Buffer_, IndexOf_, Size_, Timeout_ );
		}

		/// <summary>
		/// Find the session-object by specified session-id
		/// </summary>
		/// <param name="Id_">Session id to search</param>
		/// <returns>The TcpServerSession object; otherwise null</returns>
		public TClient Find( Guid Id_ )
		{
			foreach( TClient tss in _Clients )
			{
				if( tss.Id == Id_ )
					return tss;
			}
			return default(TClient);
		}

		#endregion

		#region Protected Functions

		/// <summary>
		/// Called if a new client is connected.
		/// </summary>
		/// <param name="Session_">New session object</param>
		protected virtual void __Connected( TClient Session_ )
		{
		}

		/// <summary>
		/// Called if a client is disconnected
		/// </summary>
		/// <param name="Session_">The disconnecting session object</param>
		protected virtual void __Disconnected( TClient Session_ )
		{
		}

		/// <summary>
		/// Dispose all resources and all clients
		/// Must called from inherit class
		/// </summary>
		/// <param name="Disposing_">Dispose managed resources</param>
		protected virtual void Dispose( bool Disposing_ )
		{
			if( !_Disposed )
			{
				if( Disposing_ )
				{
					Stop();

					if( _Lock != null )
					{
						_Lock.Dispose();
						_Lock = null;
					}

					if( _EventClientConnected != null )
					{
						_EventClientConnected.Dispose();
						_EventClientConnected = null;
					}

					if( _EventClientDisconnected != null )
					{
						_EventClientDisconnected.Dispose();
						_EventClientDisconnected = null;
					}

					if( _SendWait != null )
					{
						_SendWait.Dispose();
						_SendWait = null;
					}

					if( _ReceiveWait != null )
					{
						_ReceiveWait.Dispose();
						_ReceiveWait = null;
					}
				}
				_Disposed = true;
			}
		}

		#endregion

		#region Private Functions

		void _HandleClientDataSend( ThreadEntry Thread_, object[] Parameters_ )
		{
			try
			{
				List<TClient> clients = _Clients.CopyToNewList();
				foreach( TClient tss in clients )
					tss.HandleSend();
			}
			catch( Exception Ex_ )
			{
				Debug.WriteLine( Ex_ );
			}
		}

		void _HandleClientDataReceive( ThreadEntry Thread_, object[] Parameters_ )
		{
			try
			{
				List<TClient> clients = _Clients.CopyToNewList();
				foreach( TClient tss in clients )
					tss.HandleReceive();
			}
			catch( Exception Ex_ )
			{
				Debug.WriteLine( Ex_ );
			}
		}

		void _HandleAcceptClient( System.Net.Sockets.TcpClient Client_ )
		{
			System.Threading.Interlocked.Increment( ref _AcceptedClients );

			TClient c = (TClient)Activator.CreateInstance( typeof( TClient ), Client_ );
			_Clients.Add( c );

			c.Disconnected.Join( _HandleClientDisconnected );
			c.SendQueue.AfterAddItem.Join( _HandleSendQueueAdd );
			c.SendQueue.AfterRemoveItem.Join( _HandleSendQueueRemove );
			c.ReceiveQueue.AfterAddItem.Join( _HandleReceiveQueueAdd );
			c.ReceiveQueue.AfterRemoveItem.Join( _HandleReceiveQueueRemove );

			_EventClientConnected.Notify( this, c );
			__Connected( c );

			c.StartReceive();
		}

		void _HandleClientDisconnected( TcpServerSession Client_ )
		{
			Client_.Disconnected.Leave( _HandleClientDisconnected );
			Client_.SendQueue.AfterAddItem.Leave( _HandleSendQueueAdd );
			Client_.SendQueue.AfterRemoveItem.Leave( _HandleSendQueueRemove );
			Client_.ReceiveQueue.AfterAddItem.Leave( _HandleReceiveQueueAdd );
			Client_.ReceiveQueue.AfterRemoveItem.Leave( _HandleReceiveQueueRemove );

			_Clients.Remove( (TClient)Client_ );
			__Disconnected( (TClient)Client_ );
		}

		void _HandleSendQueueAdd( QueueExtended<TcpServerSession.SendData> Sender_, TcpServerSession.SendData Data_ )
		{
			if( Data_ != null )
				_SendWait.Increment();
		}

		void _HandleSendQueueRemove( QueueExtended<TcpServerSession.SendData> Sender_, TcpServerSession.SendData Data_ )
		{
			if( Data_ != null )
				_SendWait.Decrement();
		}

		void _HandleReceiveQueueAdd( QueueExtended<TcpServerSession.ReceiveData> Sender_, TcpServerSession.ReceiveData Data_ )
		{
			_ReceiveWait.Increment();
		}

		void _HandleReceiveQueueRemove( QueueExtended<TcpServerSession.ReceiveData> Sender_, TcpServerSession.ReceiveData Data_ )
		{
			_ReceiveWait.Decrement();
		}

		#endregion

		#region IDisposable

		/// <summary>
		/// Dispose all resources and all clients.
		/// </summary>
		public void Dispose()
		{
			Dispose( true );
			GC.SuppressFinalize( this );
		}

		#endregion
	}
}
